package com.hyt.it.ogt.kq.service.gov.arrange;

import com.hyt.core.base.BaseEntity;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.enums.ArrangeRuleEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ArrangeParamCodeEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.StringCodeEnum;
import com.hyt.it.ogt.kq.common.gov.utils.StringUtil;
import com.hyt.it.ogt.kq.service.gov.model.entity.*;
import com.hyt.it.ogt.kq.service.gov.model.param.ArrangeParam;
import com.hyt.it.ogt.kq.service.gov.model.vo.TaskPlaceVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.examiner.TimeRoomParamVO;
import com.hyt.it.ogt.kq.service.gov.schedule.ExamRoomSchedule;
import com.hyt.it.ogt.kq.service.gov.schedule.impl.ExamRoomScheduleImpl;
import com.hyt.it.ogt.kq.service.gov.schedule.model.*;
import com.hyt.loginfo.model.Organization;
import com.hyt.progress.service.ProgressManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 实体考场编排
 */
@Slf4j
@Component
public class RoomArrangeAdapter extends ArrangeAbstractService {
    @Resource
    private ArrangeByArea arrangeByArea;

    /**
     * 编排入口
     * @param param
     * @return
     */
    @Override
    public Object arrange(ArrangeParam param) {
        // 获取编排参数，并且格式化
        Map<String, String> paramMap = iTimeRoomParamService.getTimeRoomParamMap(param.getTimeRoomParamVOList(), param.getTaskId());
        ProgressManager.updateProgress(8L);
        // 编排规则
        Optional<TimeRoomParamVO> ruleOp = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ARRANGE_RULE.getCode().equalsIgnoreCase(item.getCode())).findFirst();
        int rule = Integer.valueOf(ruleOp.get().getValue());

        // 校验数据
        this.check(param);
        int count = iTimeSubjectService.countSubjectByTime(param.getTaskId());
        ScheduleConstrain scheduleConstrain = this.buildScheduleConstrain(param);
        if(count > 0 && "1".equals(scheduleConstrain.getArrangeConfig()) && scheduleConstrain.isSingleSubject){
            throw new KqException(ResponseCode.ARRANGE_MODE_NOT_SUPPORT.getCode(), ResponseCode.ARRANGE_MODE_NOT_SUPPORT
                    .getMsg());
        }
        if (ArrangeRuleEnum.AREA.getCode() == rule) { // 考区编排
            arrangeByArea.arrange(paramMap, param.getTaskId(), true);
            super.initTimeRoom(param, iTimeRoomCandidateService.lambdaQuery().eq(TimeRoomCandidate::getTaskId, param.getTaskId()).eq(BaseEntity::getDelFlag, false).list(),new HashMap<>());
        } else { // 按机构或自定义层级编排

            // 重新编排的情况下，删除所有编排数据
            timeRoomCandidateMapper.deleteArrangeCandidateByTaskId(param.getTaskId());
            ProgressManager.updateProgress(15L);
            // 构造编排算法需要的编排条件


            // 定义机构id和机构对象的映射关系
            Map<String, Organization> officeMap = new HashMap<>();

            // 构造 批次-考生-科目 关系
            List<ExamInfo> examInfos = super.getExamInfoList(param, scheduleConstrain.getGroupAttrs().size(), officeMap);

            ProgressManager.updateProgress(35L);

            // 设置总批次数
            scheduleConstrain.setBatchNumber(examInfos.size());

            // 初始化编排算法驱动对象
            ExamRoomSchedule examRoomSchedule = new ExamRoomScheduleImpl();

            // 构造 考场-科目 关系
            List<ExamRoom> examRoomList = this.getExamRoomList(param.getTaskId(), rule, scheduleConstrain.getGroupAttrs().size(), officeMap);
            ProgressManager.updateProgress(45L);
            // 通过算法计算出编排结果，考生和考场、批次绑定关系
            ImmutablePair<List<ScheduleResult>, List<ExamRoom>> result = examRoomSchedule.schedule(scheduleConstrain, examInfos, examRoomList);
            ProgressManager.updateProgress(70L);

            if (result != null && result.getLeft().size() > 0) {
                // 判断编排结果考场是否存在
                Optional<ScheduleResult> first = result.getLeft().stream().filter(item -> item.getExamRooms() != null && item.getExamRooms().size() > 0).findFirst();
                if (first.isPresent()) {
                    // 保存编排结果
                    this.saveTimeRoomCandidate(param, result.getLeft());
                } else {
                    throw new KqException(ResponseCode.ARRANGE_FAIL.getCode(), ResponseCode.ARRANGE_FAIL.getMsg());
                }
            } else {
                throw new KqException(ResponseCode.ARRANGE_FAIL.getCode(), ResponseCode.ARRANGE_FAIL.getMsg());
            }
        }
        return null;
    }

    /**
     * 获取批次-考场信息
     * @param taskId
     * @param arrangeRule
     * @param level
     * @param officeMap
     * @return
     */
    private List<ExamRoom> getExamRoomList(String taskId, int arrangeRule, int level, Map<String, Organization> officeMap) {
        // 查询所有考场
        List<ExamRoom> allRoomList = arrangeMapper.getExamRoomList(taskId, arrangeRule);
        List<TimeRoomExclude> excludeRoomList = iTimeRoomExcludeService.lambdaQuery()
                .eq(TimeRoomExclude::getTaskId, taskId).eq(BaseEntity::getDelFlag, false).list();
        // 考场绑定的批次集
        Map<String, Set<String>> excludeRoomMap = excludeRoomList.stream()
                .collect(Collectors.groupingBy(TimeRoomExclude::getRoomId,
                        Collectors.mapping(TimeRoomExclude::getTimeId, Collectors.toSet())));
        // 查询所有批次科目
        List<TimeSubject> timeSubjects = iTimeSubjectService.lambdaQuery().eq(TimeSubject::getTaskId, taskId)
                .eq(BaseEntity::getDelFlag, false).list();
        // 批次绑定的科目集
        Map<String, Set<String>> timeSubjectMap = timeSubjects.stream()
                .collect(Collectors.groupingBy(TimeSubject::getTimeId,
                        Collectors.mapping(TimeSubject::getSubjectId, Collectors.toSet())));

        // 非随机编排
        if (level > 0) {
            // 过滤符合条件的考场，要满足层级要求
            allRoomList = allRoomList.stream().filter(c -> {
                boolean flag = false;
                // 按机构层级编排
                if (ArrangeRuleEnum.DEPT.getCode() == arrangeRule) {
                    // 查询上级部门
                    this.setRoomLevelValue(c, officeMap);
                    // 自定义层级
                    if (level == 1 && StringUtils.isNotBlank(c.getLevel())) { // 1级编排
                        flag = true;
                    } else if (level == 2 && StringUtils.isNotBlank(c.getLevelTwo())) { // 2级编排
                        flag = true;
                    } else if (level == 3 && StringUtils.isNotBlank(c.getLevelThree())) { // 3级编排
                        flag = true;
                    } else if (level == 4 & StringUtils.isNotBlank(c.getLevelFour())) { // 4级编排
                        flag = true;
                    }
                } else if (ArrangeRuleEnum.SELF.getCode() == arrangeRule) {
                    // 自定义层级
                    if (level == 1 && StringUtils.isNotBlank(c.getProvince())) { // 1级编排
                        flag = true;
                    } else if (level == 2 && StringUtils.isNotBlank(c.getCity())) { // 2级编排
                        flag = true;
                    } else if (level == 3 && StringUtils.isNotBlank(c.getDistrict())) { // 3级编排
                        flag = true;
                    }
                }

                if (flag) {
                    // 考场需要考的科目
                    List<String> subjects = new ArrayList<>();
                    // 不使用该考场的所有批次
                    Set<String> excludeRoomSet = excludeRoomMap.get(c.getRoomId());
                    // 过滤科目
                    timeSubjectMap.forEach((i, k) -> {
                        if (excludeRoomSet == null) {
                            subjects.addAll(k);
                        } else if (!excludeRoomSet.contains(i)) {
                            subjects.addAll(k);
                        }
                    });

                    // 考场不考任何科目，则过滤
                    if (subjects.size() == 0) {
                        flag = false;
                    } else {
                        // 科目去重
                        c.setSubjects(new HashSet<>(subjects));
                    }
                }
                return flag;
            }).collect(Collectors.toList());
        }
        return allRoomList;
    }

    /**
     * 虚拟考场或者实体考场都在此时构造TaskRoom数据入库
     *
     * @param param
     * @param left
     */
    private void saveTimeRoomCandidate(ArrangeParam param, List<ScheduleResult> left) {
        // 任务参数
        Map<String, String> paramMap = iTimeRoomParamService.getTimeRoomParamMap(param.getTimeRoomParamVOList(), param.getTaskId());
        // 编排数据
        List<TimeRoomCandidate> timeRoomCandidates = new ArrayList<>();

        // 考生ID-准考证号 关系，为多科目同一准考证使用
        Map<String, String> admissionMap = new HashMap<>();
        // 多科目是否同一准考证
        Optional<TimeRoomParamVO> rule = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_SAME_ADMISSION_NUMBER.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        boolean sameAdmissionFlag = Boolean.valueOf(rule.get().getValue());
        ProgressManager.updateProgress(75L);

        // 批批次+考场-编排考生数量 关系映射
        Map<String, Integer> timeRoomArrangeNumMap = new HashMap<>();

        // 准考证生成方式
        Integer admissionType = new Integer(paramMap.get(StringCodeEnum.ARRANGE_PARAM_ADMISSION_TYPE.getCode()));
        // 自定义准考证，自定义准考证默认使用同一准考证
        Map<String, String> customArrangeMap = super.getCustomArrange(admissionType, param.getTaskId());
        Map<String, String> timeRoomMap = new HashMap<>();
        // 编排结果入库，允许部分编排成功，部分失败
        for (ScheduleResult item : left) { // 遍历批次
            for (ScheduleExamRoom e : item.getExamRooms()) { // 遍历考场
                String timeRoomId = UUIDUtils.randomUUID();
                // 替换横杠
                String roomId = e.getRoomId().replaceAll("-", "");
                // 考点ID，传参数时给编排算法，编排完后再返回的
                String placeId = e.getPlaceId();

                // 考点，为了生成准考证号
                TaskPlaceVO taskPlaceVO = TaskPlaceVO.builder().id(placeId).province(e.getProvince()).city(e.getCity())
                        .district(e.getDistrict()).build();

                // 批批次+考场-编排考生数量 关系
                timeRoomArrangeNumMap.put(item.getBatchNo() + "_" + roomId, e.getCandidates().size());
                if(!timeRoomMap.containsKey(item.getBatchNo() + "_" + roomId)){
                    timeRoomMap.put(item.getBatchNo() + "_" + roomId,timeRoomId);
                }
                // 遍历考生
                for (ScheduleExamRoom.ScheduleCandidate c : e.getCandidates()) {
                    // 构建编排表
                    TimeRoomCandidate timeRoomCandidate = TimeRoomCandidate.builder().taskId(item.getScheduleTaskId())
                            .timeId(item.getBatchNo())
                            .timeRoomId(timeRoomId).roomId(roomId).candidateId(c.getBmStudentId())
                            .subjectId(c.getSubject())
                            .candidateSubjectId(c.getCandidateSubjectId())
                            .seatNum(StringUtil.stringFormatZero(c.getPosition(), 3))
                            .placeId(taskPlaceVO.getId())
                            .build();
                    // 同一个考生多科目同一准考证
                    String zkz = null;
                    if (sameAdmissionFlag) {
                        // 多科目同一准考证号
                        zkz = admissionMap.get(c.getBmStudentId());
                    }
                    if (zkz == null) {
                        // 获取准考证号生成方式
                        if (ArrangeParamCodeEnum.ADMISSION_TYPE_CUSTOM.getCode().equals(admissionType)) {
                            // 使用自定义准考证号
                            zkz = customArrangeMap.get(c.getBmStudentId());
                            // 考生ID-准考证号 关系
                            admissionMap.put(c.getBmStudentId(), zkz);
                        } else {
                            // 根据规则生成准考证
                            zkz = admissionService.getAdmissionNumber(param.getTaskId(), paramMap, taskPlaceVO);
                            if (sameAdmissionFlag) { // 多科目同一个准考证号
                                // 考生ID-准考证号 关系
                                admissionMap.put(c.getBmStudentId(), zkz);
                            }
                        }
                    }
                    timeRoomCandidate.setAdmissionNum(zkz);
                    timeRoomCandidates.add(timeRoomCandidate);
                }
            }
        }
        ProgressManager.updateProgress(85L);

        if (CollectionUtils.isNotEmpty(timeRoomCandidates)) {
            // 保存编排数据
            iTimeRoomCandidateService.saveBatch(timeRoomCandidates, 1000);
            // 初始化场次表，最后执行
            super.initTimeRoom(param, timeRoomCandidates,timeRoomMap);
        }
    }

    /**
     * 设置考场等级值（机构或自定义层级）
     * @param c 考场列表
     * @param officeMap 所有机构信息
     */
    private void setRoomLevelValue(ExamRoom c, Map<String, Organization> officeMap) {
        // 根据考场绑定的机构id，获取机构信息
        Organization dept = officeMap.get(c.getLevel());
        if (dept == null) {
            dept = iUaDeptService.getDeptById(c.getLevel());
            officeMap.put(c.getLevel(), dept);
        }
        // 拆分出机构的层级（父子节点关系），有几级就赋值几个字段
        String[] split = dept.getAncestors().split(",");
        for (int i = 0; i < split.length; i++) {

            String s = split[i];
            if (i == 0) {
                c.setLevel(s);
            } else if (i == 1) {
                c.setLevelTwo(s);
            } else if (i == 2) {
                c.setLevelThree(s);
            } else if (i == 3) {
                c.setLevelFour(s);
            }
        }
        if (StringUtils.isBlank(c.getLevelTwo())) {
            c.setLevelTwo(DEFAULT);
        }
        if (StringUtils.isBlank(c.getLevelThree())) {
            c.setLevelThree(DEFAULT);
        }
        if (StringUtils.isBlank(c.getLevelFour())) {
            c.setLevelFour(DEFAULT);
        }
    }

    /**
     * 编排前校验
     * @param param
     */
    private void check(ArrangeParam param) {
        // 没有考场，不能进行实体考场编排
        Integer count = iTaskRoomService.lambdaQuery().eq(TaskRoom::getTaskId, param.getTaskId()).eq(BaseEntity::getDelFlag, false).count();
        if (count == 0) {
            throw new KqException(ResponseCode.ARRANGE_ROOM_NULL.getCode(), ResponseCode.ARRANGE_ROOM_NULL.getMsg());
        }
        // 编排规则
        Optional<TimeRoomParamVO> rule = param.getTimeRoomParamVOList().stream()
                .filter(item -> StringCodeEnum.ARRANGE_PARAM_ARRANGE_RULE.getCode().equalsIgnoreCase(item.getCode()))
                .findFirst();
        String arrangeRule = rule.get().getValue();
        // 按机构编排，考场没有区域属性，准考证不能用省市县编排
        if (String.valueOf(ArrangeRuleEnum.DEPT.getCode()).equals(arrangeRule)) {
            Optional<TimeRoomParamVO> provinceConfig = param.getTimeRoomParamVOList().stream()
                    .filter(item -> StringCodeEnum.ARRANGE_PARAM_GROUP_PROVINCE.getCode().equalsIgnoreCase(item.getCode()))
                    .findFirst();
            Optional<TimeRoomParamVO> cityConfig = param.getTimeRoomParamVOList().stream()
                    .filter(item -> StringCodeEnum.ARRANGE_PARAM_GROUP_CITY.getCode().equalsIgnoreCase(item.getCode()))
                    .findFirst();
            Optional<TimeRoomParamVO> districtConfig = param.getTimeRoomParamVOList().stream()
                    .filter(item -> StringCodeEnum.ARRANGE_PARAM_GROUP_DISTRICT.getCode().equalsIgnoreCase(item.getCode()))
                    .findFirst();
            if (Boolean.valueOf(provinceConfig.get().getValue()) || Boolean.valueOf(cityConfig.get().getValue()) || Boolean.valueOf(districtConfig.get().getValue())) {
                if (iTaskPlaceService.lambdaQuery().eq(TaskPlace::getTaskId, param.getTaskId()).eq(BaseEntity::getDelFlag, false)
                        .and(e -> e.eq(TaskPlace::getDistrict, "").or().isNull(TaskPlace::getDistrict)).count() > 0) {
                    throw new KqException(ResponseCode.ARRANGE_ADMISSION_NOT_AREA.getCode(), ResponseCode.ARRANGE_ADMISSION_NOT_AREA.getMsg());
                }
            }
        }
        super.commonCheck(param);
    }
}
